/**********************************************************************
 *
 * StackWalker.h
 *
 *
 * History:
 *  2005-07-27   v1	- First public release on http://www.codeproject.com/
 *  (for additional changes see History in 'StackWalker.cpp'!
 *
 **********************************************************************/
// #pragma once is supported starting with _MCS_VER 1000,
// so we need not to check the version (because we only support _MSC_VER >= 1100)!
#pragma once

//#include <windows.h>

// special defines for VC5/6 (if no actual PSDK is installed):
#if _MSC_VER < 1300
typedef unsigned __int64 DWORD64, *PDWORD64;
#if defined(_WIN64)
typedef unsigned __int64 SIZE_T, *PSIZE_T;
#else
typedef unsigned long SIZE_T, *PSIZE_T;
#endif
#endif  // _MSC_VER < 1300

class __declspec(dllexport) StackWalkerInternal;  // forward
class __declspec(dllexport) StackWalker
{
	public:
		typedef enum StackWalkOptions
		{
		    // No addition info will be retrived
		    // (only the address is available)
		    RetrieveNone = 0,

		    // Try to get the symbol-name
		    RetrieveSymbol = 1,

		    // Try to get the line for this symbol
		    RetrieveLine = 2,

		    // Try to retrieve the module-infos
		    RetrieveModuleInfo = 4,

		    // Also retrieve the version for the DLL/EXE
		    RetrieveFileVersion = 8,

		    // Contains all the abouve
		    RetrieveVerbose = 0xF,

		    // Generate a "good" symbol-search-path
		    SymBuildPath = 0x10,

		    // Also use the public Microsoft-Symbol-Server
		    SymUseSymSrv = 0x20,

		    // Contains all the abouve "Sym"-options
		    SymAll = 0x30,

		    // Contains all options (default)
		    OptionsAll = 0x3F
		} StackWalkOptions;

		StackWalker(
		    int options = OptionsAll, // 'int' is by design, to combine the enum-flags
		    LPCSTR szSymPath = NULL,
		    DWORD dwProcessId = GetCurrentProcessId(),
		    HANDLE hProcess = GetCurrentProcess()
		);
		StackWalker(DWORD dwProcessId, HANDLE hProcess);
		virtual ~StackWalker();

		typedef BOOL (__stdcall* PReadProcessMemoryRoutine)(
		    HANDLE	  hProcess,
		    DWORD64	 qwBaseAddress,
		    PVOID	   lpBuffer,
		    DWORD	   nSize,
		    LPDWORD	 lpNumberOfBytesRead,
		    LPVOID	  pUserData  // optional data, which was passed in "ShowCallstack"
		);

		BOOL LoadModules();

		BOOL ShowCallstack(
		    HANDLE hThread = GetCurrentThread(),
		    const CONTEXT* context = NULL,
		    PReadProcessMemoryRoutine readMemoryFunction = NULL,
		    LPVOID pUserData = NULL  // optional to identify some data in the 'readMemoryFunction'-callback
		);

#if _MSC_VER >= 1300
// due to some reasons, the "STACKWALK_MAX_NAMELEN" must be declared as "public"
// in older compilers in order to use it... starting with VC7 we can declare it as "protected"
	protected:
#endif
		enum { STACKWALK_MAX_NAMELEN = 1024 }; // max name length for found symbols

	protected:
		// Entry for each Callstack-Entry
		typedef struct CallstackEntry
		{
			DWORD64 offset;  // if 0, we have no valid entry
			CHAR name[STACKWALK_MAX_NAMELEN];
			CHAR undName[STACKWALK_MAX_NAMELEN];
			CHAR undFullName[STACKWALK_MAX_NAMELEN];
			DWORD64 offsetFromSmybol;
			DWORD offsetFromLine;
			DWORD lineNumber;
			CHAR lineFileName[STACKWALK_MAX_NAMELEN];
			DWORD symType;
			LPCSTR symTypeString;
			CHAR moduleName[STACKWALK_MAX_NAMELEN];
			DWORD64 baseOfImage;
			CHAR loadedImageName[STACKWALK_MAX_NAMELEN];
		} CallstackEntry;

		enum CallstackEntryType {firstEntry, nextEntry, lastEntry};

		virtual void OnSymInit(LPCSTR szSearchPath, DWORD symOptions, LPCSTR szUserName);
		virtual void OnLoadModule(LPCSTR img, LPCSTR mod, DWORD64 baseAddr, DWORD size, DWORD result, LPCSTR symType, LPCSTR pdbName, ULONGLONG fileVersion);
		virtual void OnCallstackEntry(CallstackEntryType eType, CallstackEntry & entry);
		virtual void OnDbgHelpErr(LPCSTR szFuncName, DWORD gle, DWORD64 addr);
		virtual void OnOutput(LPCSTR szText);

		StackWalkerInternal* m_sw;
		HANDLE m_hProcess;
		DWORD m_dwProcessId;
		BOOL m_modulesLoaded;
		LPSTR m_szSymPath;

		int m_options;

		static BOOL __stdcall myReadProcMem(HANDLE hProcess, DWORD64 qwBaseAddress, PVOID lpBuffer, DWORD nSize, LPDWORD lpNumberOfBytesRead);

		friend class StackWalkerInternal;
};


// The "ugly" assembler-implementation is needed for systems before XP
// If you have a new PSDK and you only compile for XP and later, then you can use
// the "RtlCaptureContext"
// Currently there is no define which determines the PSDK-Version...
// So we just use the compiler-version (and assumes that the PSDK is
// the one which was installed by the VS-IDE)

// INFO: If you want, you can use the RtlCaptureContext if you only target XP and later...
//	   But I currently use it in x64/IA64 environments...
//#if defined(_M_IX86) && (_WIN32_WINNT <= 0x0500) && (_MSC_VER < 1400)

#if defined(_M_IX86)
#ifdef CURRENT_THREAD_VIA_EXCEPTION
// TODO: The following is not a "good" implementation,
// because the callstack is only valid in the "__except" block...
#define GET_CURRENT_CONTEXT(c, contextFlags) \
  do { \
	memset(&c, 0, sizeof(CONTEXT)); \
	EXCEPTION_POINTERS *pExp = NULL; \
	__try { \
	  throw 0; \
	} __except( ( (pExp = GetExceptionInformation()) ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_EXECUTE_HANDLER)) {} \
	if (pExp != NULL) \
	  memcpy(&c, pExp->ContextRecord, sizeof(CONTEXT)); \
	  c.ContextFlags = contextFlags; \
  } while(0);
#else
// The following should be enough for walking the callstack...
#define GET_CURRENT_CONTEXT(c, contextFlags) \
  do { \
	memset(&c, 0, sizeof(CONTEXT)); \
	c.ContextFlags = contextFlags; \
	__asm	call x \
	__asm x: pop eax \
	__asm	mov c.Eip, eax \
	__asm	mov c.Ebp, ebp \
	__asm	mov c.Esp, esp \
  } while(0);
#endif

#else

// The following is defined for x86 (XP and higher), x64 and IA64:
#define GET_CURRENT_CONTEXT(c, contextFlags) \
  do { \
	memset(&c, 0, sizeof(CONTEXT)); \
	c.ContextFlags = contextFlags; \
	RtlCaptureContext(&c); \
} while(0);
#endif
